Перейти к основному содержимому

Коллекции в Java

Разработчику Архитектору

Коллекции в Java

Что такое коллекция

Коллекции в Java – сложный инструмент для работы с данными, который позволяет хранить, обрабатывать и манипулировать группами объектов.

Collection — это интерфейс в java.util, который задаёт общий контракт для всех коллекций, хранящих группы объектов. Через него определяются ключевые операции: добавление и удаление элементов, проверка их наличия, очистка коллекции, получение размера и итерация.

Фреймворк коллекций Java (java.util) предоставляет унифицированную архитектуру для хранения и манипулирования группами объектов. Основные компоненты:

Collection (интерфейс)
├── List (интерфейс)
│ ├── ArrayList (класс)
│ ├── LinkedList (класс)
│ └── Vector (класс)
├── Set (интерфейс)
│ ├── HashSet (класс)
│ └── TreeSet (класс)
└── Queue (интерфейс)
└── PriorityQueue (класс)

Основные реализации — List, Set и Queue.

Сам интерфейс не потокобезопасен, но его можно обернуть в синхронизированную версию через Collections.synchronizedCollection() или использовать классы из java.util.concurrent. Collection работает с дженериками, что обеспечивает типобезопасность и упрощает работу с элементами.

Важно помнить, что Map не наследуется от Collection, так как представляет пары ключ–значение и имеет свою иерархию.


Collection

Collection – корневой интерфейс, представляющий собой группу элементов (объектов). Он определяет базовые операции над набором данных: добавление, удаление, проверка наличия, размер и т.д.

Данных бывает много, и в памяти их как-то надо структурировать.

Упорядочить – вывод списка пользователей в том порядке, в котором они регистрировались (ArrayList, LinkedHashMap).

Исключать дубликаты – например, загрузка данных из разных источников и удаление повторяющихся записей (HashSet, LinkedHashSet).

Сортировать – рейтинг игроков, сортировка товаров по цене (TreeSet, TreeMap).

Хранить пары «ключ-значение» - кэширование результатов вычислений, например (HashMap, TreeMap).

Работать с очередями и приоритетами – обработка задач в фоновых потоках (PriorityQueue, Deque).

Коллекции активно используются в веб-приложениях, бизнес-логике (валидация, фильтрация), алгоритмах (поиск, сортировка), параллелизме (concurrent коллекции).

У этого интерфейса есть основные подынтерфейсы:

  • List – упорядоченная коллекция с дубликатами;
  • Set – неупорядоченная колелкция без дубликатов;
  • Queue – очередь, используется для обработки элементов по принципу FIFO (First In, First Out, первый зашел - первый вышел);
  • Deque – двусторонная очередь.
  • Map не является частью иерархии Collection, но часто рассматривается вместе с ними.

List

List – упорядоченные списки. Реализует интерфейс List<E>. Поддерживает индексацию и дублирование элементов. Существуют реализации через ArrayList, LinkedList, Vector.


ArrayList

ArrayList – реализован на основе динамического массива (Array – это массив). Имеет быстрый доступ по индексу, медленную вставку/удаление в середине.

ArrayList, как можно понять из слова Array, основан на обычном массиве (Object[]). При создании выделяется массив фиксированного начального размера (по умолчанию 10).

При добавлении элемента сверх вместимости создается новый массив большего размера (обычно в 1.5 раза) и копируются старые данные.

Пример:

List<String> list = new ArrayList<>();
list.add("Java");

Операции:

  • add(value) — добавление в конец;
  • add(index, value) — вставка в середину;
  • get(index) — чтение по индексу;
  • set(index, value) — замена по индексу;
  • remove(index) — удаление.

Давайте рассмотрим разницу между ArrayList и LinkedList:

// Демонстрация работы ArrayList (быстрый доступ по индексу)
import java.util.ArrayList;
import java.util.List;

public class ArrayListExample {
public static void main(String[] args) {
// Создаем список с начальной емкостью
List<String> users = new ArrayList<>();

// Добавление элементов
users.add("Ivan");
users.add("Maria");
users.add("Alex");

// Быстрый доступ по индексу O(1)
System.out.println("Первый пользователь: " + users.get(0));

// Вставка элемента в середину (медленная операция O(n))
users.add(1, "NewUser");

// Удаление элемента по индексу
users.remove(0);

// Итерация через цикл for (доступен только для List)
for (int i = 0; i < users.size(); i++) {
System.out.println(users.get(i));
}
}
}

ArrayList — выбор по умолчанию (80% случаев). Использовать в принципе можно всегда, особенно если в основном происходит чтение по индексу, часто проходим по списку в цикле, или не знаем, что выбрать.

List<String> names = new ArrayList<>();
names.add("Alice"); // быстро
names.add("Bob");
String name = names.get(0); // моментально

ArrayList занимает меньше памяти на элемент (только ссылка + маленький overhead массива). LinkedList значительно больше памяти на элемент: каждый узел хранит 3 ссылки (предыдущий, следующий, данные) + объект Node. Для 1 млн элементов разница может быть в 5-10 раз!

При добавлении 11-го элемента в список с capacity=10:

  • Создается новый массив на 15 элементов.
  • Копируются все 10 старых. Это дорого, но случается редко (амортизированно O(1)).

LinkedList

LinkedList – реализован как двусвязный список. Быстрая вставка/удаление, медленный доступ по индексу. Пример:

List<Integer> numbers = new LinkedList<>();
numbers.add(10);

LinkedList реализован как двусвязный список узлов (Node). Каждый узел хранит данные, ссылку на предыдущий и следующий узел.

Как устроен LinkedList внутри? Упрощённо, как-то так:

private static class Node<E> {
E item; // данные
Node<E> next; // ссылка на следующий узел
Node<E> prev; // ссылка на предыдущий узел
}

Пример:

[null] ← [Узел 1] ↔ [Узел 2] ↔ [Узел 3] → [null]
│ │ │
"Кошка" "Собака" "Попугай"
  • Первый узел (head): prev = null;
  • Последний узел (tail): next = null;
  • Каждый узел хранит три вещи: данные + две ссылки.

ArrayList при удалении из начала:

list.remove(0); // Удалит первый элемент и сдвинет все остальные — О(n)

LinkedList при get(index):

list.get(250000); // LinkedList пройдет 250000 узлов от начала — очень медленно!

Не требует непрерывного блока памяти — узлы могут быть разбросаны по куче.

Операции:

  • add(value) — добавление в конец;
  • add(index, value) — вставка в середину;
  • get(index) — чтение по индексу;
  • set(index, value) — замена по индексу;
  • remove(index) — удаление.

LinkedList является ускоспециализированной структурой, поэтому её использовать стоит, если количество операций get(index) минимально или отсутствует, если часто вставляют или удаляют элементы в середине или начале списка доступными итераторами.


Vector

Vector (устаревший) – похож на ArrayList, но синхронизированный, медленнее из-за блокировок. В современных приложениях вместо Vector используют Collections.synchronizedList() или CopyOnWriteArrayList. Пример:

List<String> legacyList = new Vector<>();

Set

Set – набор уникальных элементов. Интерфейс Set<E> гарантирует отсутствие дубликатов. Существуют реализации через HashSet, LinkedHashSet, TreeSet.

давайте рассмотрим разницу между ними:

// Демонстрация различий между реализациями Set
import java.util.*;

public class SetExamples {
public static void main(String[] args) {
// HashSet: порядок не гарантируется, дубликаты запрещены
Set<String> hashSet = new HashSet<>();
hashSet.add("Java");
hashSet.add("Python");
hashSet.add("Java"); // Этот элемент будет проигнорирован
System.out.println("HashSet размер: " + hashSet.size()); // Выведет 2

// LinkedHashSet: сохраняет порядок вставки
Set<String> linkedSet = new LinkedHashSet<>();
linkedSet.add("First");
linkedSet.add("Second");
linkedSet.add("Third");
System.out.println("LinkedHashSet порядок: " + linkedSet); // [First, Second, Third]

// TreeSet: сортирует элементы естественным образом
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(50);
treeSet.add(10);
treeSet.add(30);
System.out.println("TreeSet отсортирован: " + treeSet); // [10, 30, 50]
}
}

HashSet

HashSet хранит элементы в неопределённом порядке, использует хеш-таблицу, обладает быстрой вставкой, поиском и удалением. Пример:

Set<String> set = new HashSet<>();
set.add("Apple");

LinkedHashSet

LinkedHashSet сохраняет порядок вставки элементов. Это комбинация HashSet и связанного списка. Пример:

Set<String> orderedSet = new LinkedHashSet<>();
orderedSet.add("First");

TreeSet

TreeSet хранит элементы в отсортированном порядке, реализован на основе красно-чёрного дерева. Пример:

Set<Integer> sortedSet = new TreeSet<>();
sortedSet.add(5);

Map

Map – словарь (ключ и значение). Ключи уникальны.

Map (интерфейс)
├── HashMap (класс) - неупорядоченное отображение
├── LinkedHashMap (класс) - упорядоченное по вставке
├── TreeMap (класс) - упорядоченное по ключу
└── Hashtable (класс) - синхронизированное отображение

Пример работы с ключами, значениями и порядком:

// Демонстрация работы Map и её реализаций
import java.util.*;

public class MapExamples {
public static void main(String[] args) {
// HashMap: быстрый доступ, порядок не гарантируется
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 95);
scores.put("Bob", 87);
scores.put("Alice", 99); // Значение перезапишется
System.out.println("Значение Alice: " + scores.get("Alice")); // 99

// LinkedHashMap: сохраняет порядок вставки
Map<String, String> history = new LinkedHashMap<>();
history.put("Page1", "start");
history.put("Page2", "middle");
history.put("Page3", "end");
System.out.println("История (порядок вставки): " + history.keySet());

// TreeMap: сортировка по ключам
Map<Integer, String> days = new TreeMap<>();
days.put(3, "Wednesday");
days.put(1, "Monday");
days.put(2, "Tuesday");
System.out.println("Дни недели (по возрастанию ключей): " + days);
}
}

HashMap

HashMap – самая популярная реализация Map, позволяет один null в качестве ключа и несколько null в значениях, однако не гарантирует порядка.

Пример:

Map<String, Integer> map = new HashMap<>();
map.put("one", 1);

TreeMap

TreeMap – реализован на основе красно-чёрного дерева. Хранит пары в отсортированном по ключам порядке. Пример:

Map<String, Integer> sortedMap = new TreeMap<>();
sortedMap.put("banana", 2);

Hashtable

Hashtable – устаревшая, синхронизированная версия HashMap. Не позволяет использовать null в ключах и значениях. Вместо Hashtable лучше использовать ConcurrentHashMap или Collections.synchronizedMap(). Пример:

Map<String, String> legacyMap = new Hashtable<>();
legacyMap.put("key", "value");

LinkedHashMap

LinkedHashMap сохраняет порядок вставки элементов и полезен, когда нужно сохранять историю изменений. Пример:

Map<String, String> history = new LinkedHashMap<>();
history.put("first", "start");

Queue

Queue – очереди. Они предназначены для обработки элементов по принципу FIFO (First In – First Out). Бывают как PriorityQueue или Deque. PriorityQueue – элементы извлекаются в порядке приоритета (по значению или с помощью компаратора). Не поддерживает null. Пример:

Queue<Integer> queue = new PriorityQueue<>();
queue.offer(3);
queue.poll(); // 3 (если минимальный)

Пример работы принципов LIFO и FIFO:

// Демонстрация работы очередей и двусторонних очередей
import java.util.*;

public class QueueExamples {
public static void main(String[] args) {
// PriorityQueue: извлечение по приоритету (минимальный элемент первым)
Queue<Integer> priorityQueue = new PriorityQueue<>();
priorityQueue.offer(10);
priorityQueue.offer(2);
priorityQueue.offer(5);
System.out.println("Приоритетный элемент: " + priorityQueue.poll()); // 2

// Deque (ArrayDeque): работа как стек (LIFO) и очередь (FIFO)
Deque<String> deque = new ArrayDeque<>();

// Добавление в конец (как в очередь)
deque.offerLast("A");
deque.offerLast("B");

// Добавление в начало (как в стек)
deque.push("C");

System.out.println("Последний элемент: " + deque.peekLast()); // B
System.out.println("Первый элемент: " + deque.pop()); // C (удалено)

// Очистка очереди
while (!deque.isEmpty()) {
System.out.print(deque.poll() + " ");
}
}
}

Deque

Deque (Double Ended Queue) – можно добавлять или удалять элементы с обоих концов. Реализуется через ArrayDeque или LinkedList. Пример:

Deque<String> deque = new ArrayDeque<>();
deque.push("first");
deque.pop();

Давайте теперь разберём, когда что использовать.


Виды

ТипКогда использоватьПример
ArrayListНужен быстрый доступ по индексуСписок пользователей, список товаров в корзине
LinkedListЧастые вставки или удаления в начале или серединеРеализация очереди, стека, история действий (undo/redo)
HashSetНужны уникальные элементы, порядок не важенПроверка уникальности email, фильтрация дубликатов из базы данных
LinkedHashSetНужна уникальность и сохранение порядка вставкиИстория посещённых страниц, удаление дубликатов с сохранением порядка
TreeSetНужен отсортированный наборРейтинг игроков, очередь задач с приоритетом
HashMapОбычный словарь, скорость важнее порядкаКэширование объектов по ключу, хранение параметров конфигурации
LinkedHashMapНужен порядок вставкиЛог-файл с записями в порядке добавления, кэш LRU (Least Recently Used)
TreeMapНужен отсортированный словарьРейтинг студентов, поиск значений в диапазоне (например, дат)
PriorityQueueНужна очередь с приоритетомСистема обслуживания в банке, планировщик задач
Vector / HashtableУстаревшие, лучше избегать в новых проектахПоддержка устаревшего кода, многопоточные приложения без современных альтернатив

Общие методы коллекций

Все коллекции имеют общие методы, но не все реализации поддерживают все операции. Давайте соберём важнейшие:

  • add(E e) добавляет элемент в коллекцию, используется в List, Set, Queue, для Set игнорирует дубликаты.
  • remove(Object o) удаляет указанный элемент, используется во всех коллекциях, возвращает true, если элемент был найден.
  • contains(Object o) проверяет наличие элемента, использует equals() и hashCode().
  • containsKey() и containsValue() проверет наличие элемента для Map;
  • get(int index) получает элемент по индексу, используется только в List, причем в ArrayList – делает быстро, в LinkedList – медленно.
  • put(K key, V value) добавляет пару ключ-значение, используется в Map, перезаписывает значение, если ключ уже есть.
  • get(Object key) получает значение по ключу, используется в Map, возвращает null, если ключ не найден.
  • poll() извлекает и удаляет первый элемент, используется в Queue, Deque, возвращает null, если очередь пуста.
  • offer(E e) добавляет элемент в очередь, используется в Queue, возвращает false, если не удалось добавить.
  • peek() возвращает, но не удаляет первый элемент, используется в Queue, полезно для проверки перед извлечением.

Примеры:

// ArrayList
List<String> list = new ArrayList<>();
list.add("Java");
list.add(0, "Hello"); // вставка в начало
System.out.println(list.get(1)); // Java

// HashSet
Set<Integer> uniqueNumbers = new HashSet<>();
uniqueNumbers.add(1);
uniqueNumbers.add(1); // дубликат игнорируется
System.out.println(uniqueNumbers.contains(1)); // true

// HashMap
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 90);
System.out.println(scores.get("Alice")); // 90

// PriorityQueue
Queue<Integer> queue = new PriorityQueue<>();
queue.offer(5);
queue.offer(3);
System.out.println(queue.poll()); // 3 (минимальный элемент)

См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).